package net.greyar.theory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class Symbol {
	protected String token = "";
	protected String print = "";
	protected List<Symbol> symbols = new ArrayList<Symbol>();
	public int size() {
		return symbols.size();
	}
	public Symbol get(int i) {
		return symbols.get(i);
	}
	public void add(Symbol symbol) {
		symbols.add(symbol);
	}
	@Override
	public String toString() {
		return token;
	}
	public String getToken() {
		return token;
	}
	public String getPrint() {
		return print;
	}
	public abstract String getKey();
	protected abstract StringBuilder toString(StringBuilder sb);
	protected abstract StringBuilder getPrint(StringBuilder sb);
	public abstract boolean occurs(Variable variable);
	public abstract Substitution unify(Symbol other, Substitution sigma);
	public Symbol apply(Substitution sigma) {
		return apply(sigma, new HashMap<String, Variable>());
	}
	protected abstract Symbol apply(Substitution sigma, Map<String, Variable> variables);
	public List<Variable> getVariables() {
		return getVariables(new ArrayList<Variable>());
	}
	protected abstract List<Variable> getVariables(List<Variable> variables);
	protected abstract Symbol level();
	protected abstract boolean grounds(Substitution sigma);
	public List<Symbol> getSymbols() {
		return symbols;
	}
	protected Symbol strings() {
		toString(new StringBuilder());
		getPrint(new StringBuilder());
		return this;
	}
	public static List<Symbol> parseMany(String gdl) {
		List<Symbol> all = new ArrayList<Symbol>();
		int i = 0;
		while (i < gdl.length()) {
			Symbol symbol = new Expression();
			char ch = gdl.charAt(i);
			if (Character.isWhitespace(ch)) {
				i++;
			} else if (ch == ';') {
				i = comment(gdl, i);
			} else if (ch == '(') {
				i = expression(gdl, i + 1, symbol.getSymbols(), new HashMap<String, Variable>());
				all.add(symbol.level().strings());
			} else if (ch == ')') {
				break;
			} else if (ch == '?') {
				i = variable(gdl, i, symbol.getSymbols(), new HashMap<String, Variable>());
				all.add(symbol.level().strings());
			} else if (ch == '\'') {
				i = quote(gdl, i, symbol.getSymbols());
				all.add(symbol.level().strings());
			} else {
				i = literal(gdl, i, symbol.getSymbols());
				all.add(symbol.level().strings());
			}
		}
		return all;
	}
	public static Symbol parse(String gdl) {
		Symbol symbol = new Expression();
		Map<String, Variable> variables = new HashMap<String, Variable>();
		int i = 0;
		while (i < gdl.length()) {
			char ch = gdl.charAt(i);
			if (Character.isWhitespace(ch)) {
				i++;
			} else if (ch == ';') {
				i = comment(gdl, i);
			} else if (ch == '(') {
				i = expression(gdl, i + 1, symbol.getSymbols(), variables);
			} else if (ch == ')') {
				break;
			} else if (ch == '?') {
				i = variable(gdl, i, symbol.getSymbols(), variables);
			} else if (ch == '\'') {
				i = quote(gdl, i, symbol.getSymbols());
			} else {
				i = literal(gdl, i, symbol.getSymbols());
			}
		}
		return symbol.level().strings();
	}
	private static int comment(String gdl, int i) {
		while (i < gdl.length()) {
			if (gdl.charAt(i) == '\n') {
				return i;
			}
			i++;
		}
		return i;
	}
	private static int expression(String gdl, int i, List<Symbol> symbols, Map<String, Variable> variables) {
		Symbol symbol = operator(gdl, i);
		while (i < gdl.length()) {
			char ch = gdl.charAt(i);
			if (Character.isWhitespace(ch)) {
				i++;
			} else if (ch == ';') {
				i = comment(gdl, i);
			} else if (ch == '(') {
				i = expression(gdl, i + 1, symbol.getSymbols(), variables);
			} else if (ch == ')') {
				symbols.add(symbol);
				return i + 1;
			} else if (ch == '?') {
				i = variable(gdl, i, symbol.getSymbols(), variables);
			} else if (ch == '\'') {
				i = quote(gdl, i, symbol.getSymbols());
			} else {
				i = literal(gdl, i, symbol.getSymbols());
			}
		}
		return i;
	}
	private static int variable(String gdl, int i, List<Symbol> symbols, Map<String, Variable> variables) {
		int j = i + 1;
		while (j < gdl.length()) {
			char ch = gdl.charAt(j);
			if (ch == '(' || ch == ')' || Character.isWhitespace(ch)) {
				break;
			} else {
				j++;
			}
		}
		String variable = gdl.substring(i, j);
		if (!variables.containsKey(variable)) {
			variables.put(variable, new Variable(variable));
		}
		symbols.add(variables.get(variable));
		return j;
	}
	private static int literal(String gdl, int i, List<Symbol> symbols) {
		int j = i + 1;
		while (j < gdl.length()) {
			char ch = gdl.charAt(j);
			if (ch == '(' || ch == ')' || Character.isWhitespace(ch)) {
				break;
			} else {
				j++;
			}
		}
		symbols.add(new Literal(gdl.substring(i, j)));
		return j;
	}
	private static int quote(String gdl, int i, List<Symbol> symbols) {
		int j = i + 1;
		while (j < gdl.length()) {
			char ch = gdl.charAt(j);
			if (ch == '\'' && gdl.charAt(j - 1) != '\\') {
				j++;
				break;
			} else {
				j++;
			}
		}
		symbols.add(new Quote(gdl.substring(i, j)));
		return j;
	}
	private static Symbol operator(String gdl, int i) {
		int j = i;
		while (j < gdl.length()
				&& gdl.charAt(j) != '('
				&& gdl.charAt(j) != ')'
				&& !Character.isWhitespace(gdl.charAt(j))) {
					j++;
				}
		String operator = gdl.substring(i, j);
		if (operator.equals("if")) {
			return new Rule();
		} else if (operator.equals("not")) {
			return new Not();
		} else if (operator.equals("equal")) {
			return new Equal();
		} else if (operator.equals("distinct")) {
			return new Distinct();
		} else if (operator.equals("and")) {
			return new And();
		} else if (operator.equals("or")) {
			return new Or();
		} else if (operator.equals("sum")) {
			return new Sum();
		} else if (operator.equals("mult")) {
			return new Multiplication();
		} else if (operator.equals("neg")) {
			return new Negative();
		} else if (operator.equals("abs")) {
			return new Absolute();
		} else if (operator.equals("random")) {
			return new Random();
		} else {
			return new Expression();
		}
	}
}
